package zdream.rockchronicle.core.input;

import java.util.ArrayList;
import java.util.Arrays;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.ControllerAdapter;
import com.badlogic.gdx.controllers.PovDirection;
import com.badlogic.gdx.utils.IntIntMap;

import zdream.control.input.IMapKeyListener;
import zdream.control.input.WorldInput;

/**
 * <p>玩家的键位设置.
 * <p>这里设置的是单个玩家或控制源的键位输入.
 * 控制游戏时, 键盘输入和手柄输入可以同时监听.
 * </p>
 * 
 * @author Zdream
 */
public class PlayerInput implements WorldInput {

	/**
	 * @param seq
	 *  序号. 其实就是你是 1P 还是 2P 的意思
	 */
	public PlayerInput(final byte seq) {
		this.seq = seq;
		defaultKeyMap();
		defaultCtrlMap();

		initTransmitArray();
	}

	/**
	 * 是否启用
	 */
	boolean enable = true;

	public boolean isEnable() {
		return enable;
	}
	public void setEnable(boolean enable) {
		this.enable = enable;
	}

	public static final byte PLAYER_1P = 1;
	public static final byte PLAYER_2P = 2;
	/**
	 * 序号, 指示你是 1P 还是 2P
	 */
	public final byte seq;

	private static final int BTN_KEY_LEN = 15;

	/**
	 * 手柄按钮键位 (手柄的方向键位不会改的)<br>
	 * 游戏键位 - 物理键位, 如: [W] 代表上
	 */
	int[] btnCtrlMap = new int[BTN_KEY_LEN];

	/* **********
	 * callback *
	 ********** */

	// 触发消息 callbacks
	protected ArrayList<IMapKeyListener>[] transmitDirection;
	protected ArrayList<IMapKeyListener>[] transmitWeapon;
	protected ArrayList<IMapKeyListener>[] transmitButton;

	@SuppressWarnings("unchecked")
	private void initTransmitArray() {
		transmitDirection = new ArrayList[5];
		for (int i = 1; i < 5; i++) {
			transmitDirection[i] = new ArrayList<>();
		}

		transmitWeapon = new ArrayList[9];
		for (int i = 1; i < 9; i++) {
			transmitWeapon[i] = new ArrayList<>();
		}

		transmitButton = new ArrayList[BTN_KEY_LEN];
		for (int i = 1; i < BTN_KEY_LEN; i++) {
			transmitButton[i] = new ArrayList<>();
		}
	}

	/* **********
	 *   键位   *
	 ********** */
	/**
	 * 键盘方向键位
	 * 索引 1 分别是 null、上下左右;
	 * 每个第一索引指向的一维数组就是全部的控制这个方向的键位.<br>
	 *
	 * 例如 dirKeyMap[InputCenter.MAP_UP] 是这样的一个数组 [Keys.W, Keys.UP],
	 * 就是说 W 和 UP 两个键位都是控制它的
	 */
	int[][] dirKeyMap = new int[5][3];

	/**
	 * 键盘其它键位 (除方向键外的所有键)
	 */
	int[][] btnKeyMap = new int[BTN_KEY_LEN][3];

	/**
	 * 切换武器的键位. weaponKeyMap[0] 是无效的
	 */
	int[][] weaponKeyMap = new int[9][3];

	/**
	 * 这里建立了一个 value 到 key 的映射 (键盘)<br>
	 * 物理键位 - 游戏键位, 如: [W] 代表上
	 */
	IntIntMap mapping = new IntIntMap();

	void putKeyMap(int mapKey, int[] keyCodes) {
		if (mapKey < 0x10) {
			System.arraycopy(keyCodes, 0, dirKeyMap[mapKey], 0, keyCodes.length);
		} else if (mapKey < 0x20) {
			System.arraycopy(keyCodes, 0, weaponKeyMap[mapKey & 0xF], 0, keyCodes.length);
		} else {
			System.arraycopy(keyCodes, 0, btnKeyMap[mapKey & 0xF], 0, keyCodes.length);
		}

		for (int i = 0; i < keyCodes.length; i++) {
			if (mapping.get(keyCodes[i], -1) != -1) {
				throw new IllegalArgumentException("键位 " + keyCodes[i] + " 已经被占用");
			}
			mapping.put(keyCodes[i], mapKey);
		}
	}

	/**
	 * @param index
	 *  因为每个 mapKey 可以绑定三个键, 你可以选择第几个键进行替换. 范围是 0 ~ 2
	 */
	public void putKeyMap(int mapKey, int keyCode, int index) {
		int existsMapKey = mapping.get(keyCode, -1);
		if (existsMapKey != -1) {
			// 先删掉原来的 key codes
			int[] keyMaps;
			if (existsMapKey < 0x10) {
				keyMaps = dirKeyMap[existsMapKey];
			} else if (existsMapKey < 0x20) {
				keyMaps = weaponKeyMap[existsMapKey & 0xF];
			} else {
				keyMaps = btnKeyMap[existsMapKey & 0xF];
			}

			for (int i = 0; i < keyMaps.length; i++) {
				if (keyMaps[i] == keyCode) {
					keyMaps[i] = 0;
				}
			}
		}

		mapping.put(keyCode, mapKey);
		int[] keyMaps;
		if (mapKey < 0x10) {
			keyMaps = dirKeyMap[mapKey];
		} else if (mapKey < 0x20) {
			keyMaps = weaponKeyMap[mapKey & 0xF];
		} else {
			keyMaps = btnKeyMap[mapKey & 0xF];
		}
		keyMaps[index] = keyCode;
	}

	public int[] queryKeyMap(int mapKey) {
		if (mapKey < 0x10) {
			return Arrays.copyOf(dirKeyMap[mapKey], 3);
		} else if (mapKey < 0x20) {
			return Arrays.copyOf(weaponKeyMap[mapKey & 0xF], 3);
		} else {
			return Arrays.copyOf(btnKeyMap[mapKey & 0xF], 3);
		}
	}

	void defaultKeyMap() {
		mapping.clear();

		putKeyMap(MAP_UP, new int[] {Keys.W, Keys.UP});
		putKeyMap(MAP_DOWN, new int[] {Keys.S, Keys.DOWN});
		putKeyMap(MAP_LEFT, new int[] {Keys.A, Keys.LEFT});
		putKeyMap(MAP_RIGHT, new int[] {Keys.D, Keys.RIGHT});
		putKeyMap(MAP_BACK, new int[] {Keys.X, Keys.TAB, Keys.BACKSPACE});
		putKeyMap(MAP_START, new int[] {Keys.Z, Keys.ENTER, Keys.ESCAPE});
		putKeyMap(MAP_ATTACK, new int[] {Keys.J});
		putKeyMap(MAP_JUMP, new int[] {Keys.K, Keys.SPACE});

		putKeyMap(MAP_SPECIAL, new int[] {Keys.I});
		putKeyMap(MAP_RUSH, new int[] {Keys.U});
		putKeyMap(MAP_STRONG, new int[] {Keys.H});
		putKeyMap(MAP_LT, new int[] {Keys.Q});
		putKeyMap(MAP_RT, new int[] {Keys.E});

		putKeyMap(MAP_PROP1, new int[] {Keys.V});
		putKeyMap(MAP_PROP2, new int[] {Keys.B});
		putKeyMap(MAP_PROP3, new int[] {Keys.N});
		putKeyMap(MAP_DETAIL, new int[] {Keys.C});

		putKeyMap(MAP_WEAPON1, new int[] {Keys.NUM_1});
		putKeyMap(MAP_WEAPON2, new int[] {Keys.NUM_2});
		putKeyMap(MAP_WEAPON3, new int[] {Keys.NUM_3});
		putKeyMap(MAP_WEAPON4, new int[] {Keys.NUM_4});
		putKeyMap(MAP_WEAPON5, new int[] {Keys.NUM_5});
		putKeyMap(MAP_WEAPON6, new int[] {Keys.NUM_6});
		putKeyMap(MAP_WEAPON7, new int[] {Keys.NUM_7});
		putKeyMap(MAP_WEAPON8, new int[] {Keys.NUM_8});
	}

	void defaultCtrlMap() {
		btnCtrlMap[MAP_BACK & 0xF] = InputCenter.XBOX_BACK;
		btnCtrlMap[MAP_START & 0xF] = InputCenter.XBOX_START;
		btnCtrlMap[MAP_ATTACK & 0xF] = InputCenter.XBOX_X;
		btnCtrlMap[MAP_JUMP & 0xF] = InputCenter.XBOX_A;
		btnCtrlMap[MAP_SPECIAL & 0xF] = InputCenter.XBOX_Y;
		btnCtrlMap[MAP_STRONG & 0xF] = InputCenter.XBOX_B;
		btnCtrlMap[MAP_RUSH & 0xF] = InputCenter.XBOX_RB;
		btnCtrlMap[MAP_LT & 0xF] = InputCenter.XBOX_LT;
		btnCtrlMap[MAP_RT & 0xF] = InputCenter.XBOX_RT;
		btnCtrlMap[MAP_LB & 0xF] = -1;
		btnCtrlMap[MAP_PROP1 & 0xF] = -1;
		btnCtrlMap[MAP_PROP2 & 0xF] = -1;
	}

	/* **********
	 * 真实设备 *
	 ********** */

	/**
	 * 是否屏蔽真实的键盘 / 手柄的响应.
	 * 在测试时需要模拟, 会设置屏蔽真实的键盘 / 手柄响应
	 */
	public boolean maskRealDevice = false;

	/**
	 * 手柄实体
	 */
	Controller ctrl;

	public Controller getController() {
		return ctrl;
	}
	/**
	 * 设置将由键盘控制角色
	 */
	public void bindKeyboard() {
		if (this.ctrl != null) {
			Gdx.app.log("InputCenter", "用户 " + seq + " 解除绑定手柄: " + ctrl.getName());
			ctrl.removeListener(cl);
			this.ctrl = null;
		}
	}
	/**
	 * 设置将由控制器控制角色 (控制器和键盘均能控制)
	 */
	public void bindController(Controller ctrl) {
		this.ctrl = ctrl;

		Gdx.app.log("InputCenter", "用户 " + seq + " 绑定键盘与手柄: " + ctrl.getName());
		ctrl.addListener(cl);
	}

	public Controller getCtrl() {
		return ctrl;
	}

	/**
	 * 接收到键盘的按键响应
	 * @param keyCode 见 com.badlogic.gdx.Input.Keys
	 */
	void keyPressed(int keyCode) {
		if (maskRealDevice) {
			return;
		}

		int i = mapping.get(keyCode, -1);
		if (i == -1) {
			return;
		}

		transmitPressed(i);
	}

	/**
	 * 接收到键盘的释放响应
	 * @param keyCode 见 com.badlogic.gdx.Input.Keys
	 */
	void keyReleased(int keyCode) {
		if (maskRealDevice) {
			return;
		}

		int i = mapping.get(keyCode, -1);
		if (i == -1) {
			return;
		}
		
		transmitReleased(i);
	}

	/**
	 * 由用户调用
	 * @param l callback
	 * @param mapKeys 监听的键位
	 */
	public void addControlListener(IMapKeyListener l, int... mapKeys) {
		int key;
		for (int i = 0; i < mapKeys.length; i++) {
			key = mapKeys[i];
			if (key < 0x10) {
				transmitDirection[key].add(l);
			} else if (key < 0x20) {
				transmitWeapon[key & 0xF].add(l);
			} else {
				transmitButton[key & 0x1F].add(l);
			}
		}
	}

	/**
	 * 清除这个 IControlListener 的所有绑定
	 */
	public void removeControlListener(IMapKeyListener l) {
		int length = transmitDirection.length;
		for (int i = 1; i < length; i++) {
			transmitDirection[i].remove(l);
		}

		length = transmitButton.length;
		for (int i = 1; i < length; i++) {
			transmitButton[i].remove(l);
		}

		length = transmitWeapon.length;
		for (int i = 1; i < length; i++) {
			transmitWeapon[i].remove(l);
		}
	}

	private void transmitPressed(int mapKey) {
		ArrayList<IMapKeyListener> ls;
		if (mapKey < 0x10) {
			ls = transmitDirection[mapKey];
		} else if (mapKey < 0x20) {
			ls = transmitWeapon[mapKey & 0xF];
		} else {
			ls = transmitButton[mapKey & 0xF];
		}

		int len = ls.size();
		for (int i = 0; i < len; i++) {
			IMapKeyListener l = ls.get(i);
			l.onKeyPressed(mapKey, this);
		}
	}

	private void transmitReleased(int mapKey) {
		ArrayList<IMapKeyListener> ls;
		if (mapKey < 0x10) {
			ls = transmitDirection[mapKey];
		} else if (mapKey < 0x20) {
			ls = transmitWeapon[mapKey & 0xF];
		} else {
			ls = transmitButton[mapKey & 0xF];
		}

		int len = ls.size();
		for (int i = 0; i < len; i++) {
			IMapKeyListener l = ls.get(i);
			l.onKeyReleased(mapKey, this);
		}
	}

	/*
	 * Xbox 手柄说明:
	 * 
	 * --- 键位按下:
	 * A : button 0
	 * B : button 1
	 * X : button 2
	 * Y : button 3
	 * LB : button 4
	 * RB : button 5
	 * Back : button 6
	 * Start : button 7
	 * 左摇杆 : button 8
	 * 右摇杆 : button 9
	 * 
	 * --- 十字键
	 * axisMoved,
	 * 方向是八个再加没有动静, 共 9 种可能
	 * 
	 * --- 左右摇杆
	 * axisMoved,
	 * 当左摇杆上下移动 (y 变化), axisCode = 0, 上为负, 下为正, 范围 -1 至 1
	 * 当左摇杆左右移动 (x 变化), axisCode = 1, 左为负, 右为正, 范围 -1 至 1
	 * 当右摇杆上下移动 (y 变化), axisCode = 2, 上为负, 下为正, 范围 -1 至 1
	 * 当右摇杆左右移动 (x 变化), axisCode = 3, 左为负, 右为正, 范围 -1 至 1
	 * 
	 * --- 扳机
	 * axisMoved,
	 * 左右扳机合起来操纵 axisCode = 4 一个值, 具体是:
	 * 左扳机为正 (0 ~ 1), 右扳机为负 (-1 ~ 0), 如果两个一起按, axisCode = 左 + 右
	 */

	/**
	 * 只用于检测连接是否断了. 如果断了, 则换回键盘
	 */
	ControllerAdapter cl = new ControllerAdapter() {
		@Override
		public void disconnected(Controller controller) {
			PlayerInput.this.bindKeyboard();
		}
	};

	private boolean isMapKeyDownForRealDevice(int mapKey) {
		// 键盘部分
		int[] keys;

		if (mapKey < 0xF) {
			// 方向
			keys = dirKeyMap[mapKey];
		} else if (mapKey < 0x1F) {
			keys = weaponKeyMap[mapKey & 0xF];
		} else {
			// button
			keys = btnKeyMap[mapKey & 0xF];
		}

		for (int i = 0; i < keys.length; i++) {
			if (Gdx.input.isKeyPressed(keys[i])) {
				return true;
			}
		}

		// 手柄部分
		if (ctrl == null) {
			return false;
		}

		if (mapKey < 0xF) {
			// 现在确定方向. 十字键与左摇杆都可以, 但是两者会冲突. TODO 需要有配置确定用十字键和摇杆
			// 如果 Pov 有效, 则按照 Pov, 否则 Axis
			PovDirection dir = ctrl.getPov(0);
			if (dir != PovDirection.center) {
				switch (mapKey) {
				case MAP_UP:
					return (dir == PovDirection.north || dir == PovDirection.northEast || dir == PovDirection.northWest);
				case MAP_DOWN:
					return (dir == PovDirection.south || dir == PovDirection.southEast || dir == PovDirection.southWest);
				case MAP_LEFT:
					return (dir == PovDirection.west || dir == PovDirection.northWest || dir == PovDirection.southWest);
				case MAP_RIGHT:
					return (dir == PovDirection.east || dir == PovDirection.northEast || dir == PovDirection.southEast);
				default:
					return false;
				}
			}

			// Axis
			float y = ctrl.getAxis(0);
			float x = ctrl.getAxis(1);

			switch (mapKey) {
			case MAP_UP:
				return y < -0.2f;
			case MAP_DOWN:
				return y > 0.2f;
			case MAP_LEFT:
				return x < -0.2f;
			case MAP_RIGHT:
				return x > 0.2f;

			default:
				return false;
			}
		} else {
			// button
			int key = btnCtrlMap[mapKey & 0xF];
			if (key < 0) { // 没有对应的键位
				return false;
			}
			if (key > 0xFF) {
				// TODO 虚拟键位
				return false;
			}
			return ctrl.getButton(key);
		}
	}

	/* **********
	 * 虚拟设备 *
	 ********** */

	/**
	 * 是否屏蔽虚拟的键盘 / 手柄的响应.
	 */
	public boolean maskVirtualDevice = false;

	public final IntIntMap virtualMapKeyPressedMap = new IntIntMap();
	{
		initVirtualMapKeyPressedMap();
	}

	private void initVirtualMapKeyPressedMap() {
		virtualMapKeyPressedMap.clear();
		virtualMapKeyPressedMap.put(MAP_UP, 0);
		virtualMapKeyPressedMap.put(MAP_DOWN, 0);
		virtualMapKeyPressedMap.put(MAP_LEFT, 0);
		virtualMapKeyPressedMap.put(MAP_RIGHT, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON1, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON2, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON3, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON4, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON5, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON6, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON7, 0);
		virtualMapKeyPressedMap.put(MAP_WEAPON8, 0);
		virtualMapKeyPressedMap.put(MAP_BACK, 0);
		virtualMapKeyPressedMap.put(MAP_START, 0);
		virtualMapKeyPressedMap.put(MAP_ATTACK, 0);
		virtualMapKeyPressedMap.put(MAP_JUMP, 0);
		virtualMapKeyPressedMap.put(MAP_SPECIAL, 0);
		virtualMapKeyPressedMap.put(MAP_STRONG, 0);
		virtualMapKeyPressedMap.put(MAP_LB, 0);
		virtualMapKeyPressedMap.put(MAP_RUSH, 0);
		virtualMapKeyPressedMap.put(MAP_LT, 0);
		virtualMapKeyPressedMap.put(MAP_RT, 0);
		virtualMapKeyPressedMap.put(MAP_PROP1, 0);
		virtualMapKeyPressedMap.put(MAP_PROP2, 0);
		virtualMapKeyPressedMap.put(MAP_PROP3, 0);
		virtualMapKeyPressedMap.put(MAP_DETAIL, 0);
	}

	public boolean isVirtualKeyPressed(int keyCode) {
		return virtualMapKeyPressedMap.get(keyCode, 0) == 1;
	}

	public void setVirtualKeyPressed(int keyCode) {
		virtualMapKeyPressedMap.put(keyCode, 1);
	}

	public void setVirtualKeyReleased(int keyCode) {
		virtualMapKeyPressedMap.put(keyCode, 0);
	}

	public void resetVirtualKeyMap() {
		initVirtualMapKeyPressedMap();
	}

	/* **********
	 *   判定   *
	 ********** */

	/**
	 * 配置了多个按键, 只要有一个按下了就生效
	 * 配置了手柄的, 键盘按了也有效果
	 */
	public boolean isMapKeyDown(int mapKey) {
		if (!maskRealDevice && isMapKeyDownForRealDevice(mapKey)) {
			return true;
		}
		return !maskVirtualDevice && isVirtualKeyPressed(mapKey);
	}
}
